#include "OpenGLWindow.h"

#include "GL/Glew.h"
#include "GL/wglew.h"

#include <math.h>
#include "soil.h"
#include "RenderComponent.h"
#include "VertexColorCube.h"

OpenGLWindow::OpenGLWindow(std::wstring title,unsigned int width, unsigned int height):
	Window(title,width,height),

	m_Rotation(0),
	m_Running(true),
	m_Bits(32),
	m_Fullscreen(false),
	m_FullscreenState(false)
{

	m_RenderComponents.push_back(new VertexColorCube(0.2f,0.2f,0.4f));
}


OpenGLWindow::~OpenGLWindow(void)
{

}

void OpenGLWindow::Repaint()
{
	AcquirePaintLock();
	Draw();
	::SwapBuffers(m_hDC);
	glFinish();
	ReleasePaintLock();
}

void OpenGLWindow::Run()
{
	wglMakeCurrent(m_hDC,m_hRC);
	Init();
	Resize(m_Width,m_Height);

	while( m_Running ){
		wglMakeCurrent(m_hDC,m_hRC);
		Repaint();
	}

	if (m_hRC)
	{
		// make sure the rendering context is no longer current.
		wglMakeCurrent(NULL,NULL);
		// delete the rendering context
		wglDeleteContext(m_hRC);
		m_hRC=NULL;
	}
}

bool OpenGLWindow::Init()
{
	GLenum err = glewInit();
	bool m_GlewOK = (err == GLEW_OK);

	glShadeModel(GL_SMOOTH);							// Enable Smooth Shading
	glClearColor(0.0f, 0.0f, 0.0f, 0.5f);				// Black Background
	glClearDepth(1.0f);									// Depth Buffer Setup
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing
	glDepthFunc(GL_LEQUAL);								// The Type Of Depth Testing To Do
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);	// Really Nice Perspective Calculations

	wglSwapIntervalEXT(1);
	m_BoxTextureID = SOIL_load_OGL_texture
	(
		"cube.jpg",
		SOIL_LOAD_AUTO,
		SOIL_CREATE_NEW_ID,
		SOIL_FLAG_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
	);
	return true;
}

void OpenGLWindow::Resize(unsigned int width, unsigned int height)
{
	m_Width= width;
	m_Height = height > 0 ? height:1;

	glViewport(0,0,m_Width,m_Height);						// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	// Calculate The Aspect Ratio Of The Window
	gluPerspective(45.0f,(GLfloat)width/(GLfloat)height,0.1f,100.0f);
	gluLookAt(1,1,1,0,0,0,0,0,1);

	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix
}

void OpenGLWindow::SetFullScreen(bool fullScreen)
{
	if ( fullScreen == m_FullscreenState )
		return;
	m_Fullscreen = fullScreen;
	AcquirePaintLock();
	if (fullScreen && !m_FullscreenState)
	{
		SwitchToFullscreen();
	}else if ( !fullScreen && m_FullscreenState )
	{
		SwitchToWindowed();
	}
	ReleasePaintLock();
}

void OpenGLWindow::SwitchToFullscreen()
{
	DEVMODE dmScreenSettings;
	memset(&dmScreenSettings,0,sizeof(dmScreenSettings));
	dmScreenSettings.dmSize			=sizeof(dmScreenSettings);
	dmScreenSettings.dmPelsWidth	= m_Width;
	dmScreenSettings.dmPelsHeight	= m_Height;
	dmScreenSettings.dmBitsPerPel	= m_Bits;
	dmScreenSettings.dmFields=DM_BITSPERPEL|DM_PELSWIDTH|DM_PELSHEIGHT;

	SetWindowLong(m_hWnd,GWL_EXSTYLE, WS_EX_APPWINDOW );
	SetWindowLong(m_hWnd,GWL_STYLE, WS_POPUP );

	if (ChangeDisplaySettings(&dmScreenSettings,CDS_FULLSCREEN)!=DISP_CHANGE_SUCCESSFUL)
	{
		m_FullscreenState = false;
	}else{
		m_FullscreenState = true;
		ShowCursor(FALSE);
		ShowWindow(m_hWnd,SW_SHOW);
		SetForegroundWindow(m_hWnd);
	}
}

void OpenGLWindow::SwitchToWindowed()
{
	SetWindowLong(m_hWnd,GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE  );
	SetWindowLong(m_hWnd,GWL_STYLE, WS_OVERLAPPEDWINDOW);
	ChangeDisplaySettings(NULL,0);
	ShowCursor(TRUE);
	ShowWindow(m_hWnd,SW_SHOW);
	SetForegroundWindow(m_hWnd);
	m_FullscreenState = false;
}

void OpenGLWindow::ToggleFullScreen(){
	SetFullScreen(!m_FullscreenState);
}


void OpenGLWindow::AcquirePaintLock(){
	  EnterCriticalSection(&m_PaintCS);
}

void OpenGLWindow::ReleasePaintLock(){
	  LeaveCriticalSection(&m_PaintCS);
}

bool OpenGLWindow::Create()
{
	InitializeCriticalSection(&m_PaintCS);

	WNDCLASSEX	wc;
	this->CreateDefaultWindowClass(wc);

	DWORD dwExStyle, dwStyle ;
	if (m_Fullscreen)
	{
		dwExStyle=WS_EX_APPWINDOW;
		dwStyle=WS_POPUP;
	}
	else
	{
		dwExStyle=WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
		dwStyle=WS_OVERLAPPEDWINDOW;
	}

	CreateStyledWindow(dwExStyle,dwStyle);
	SetFullScreen(m_Fullscreen);

	GLuint		PixelFormat;
	static	PIXELFORMATDESCRIPTOR pfd=
	{
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW |
		PFD_SUPPORT_OPENGL |
		PFD_DOUBLEBUFFER,
		PFD_TYPE_RGBA,
		m_Bits,
		0, 0, 0, 0, 0, 0,
		0,
		0,
		0,
		0, 0, 0, 0,
		16,
		0,
		0,
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};

	if (!(m_hDC=GetDC(m_hWnd)))
	{
		Kill();
		MessageBox(NULL,L"Can't Create A GL Device Context.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	if (!(PixelFormat=ChoosePixelFormat(m_hDC,&pfd)))
	{
		Kill();
		MessageBox(NULL,L"Can't Find A Suitable PixelFormat.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	if(!SetPixelFormat(m_hDC,PixelFormat,&pfd))
	{
		Kill();
		MessageBox(NULL,L"Can't Set The PixelFormat.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	if (!(m_hRC=wglCreateContext(m_hDC)))
	{
		Kill();
		MessageBox(NULL,L"Can't Create A GL Rendering Context.",L"ERROR",MB_OK|MB_ICONEXCLAMATION);
		return false;
	}

	ShowWindow(m_hWnd,SW_SHOW);
	SetForegroundWindow(m_hWnd);
	SetFocus(m_hWnd);

	m_pPaintThread =new Thread(this);
	m_pPaintThread->start();

	return true;
}

bool OpenGLWindow::Kill()
{
	m_Running = false;
	bool killSuccess = true;
	if (m_hDC && !ReleaseDC(m_hWnd,m_hDC))
	{
		killSuccess = false;
	}

	if (m_hWnd && !DestroyWindow(m_hWnd))
	{
		killSuccess = false;
	}

	if (!UnregisterClass(L"Window",m_hInstance))
	{
		killSuccess = false;
	}

	m_hDC=NULL;
    m_hWnd=NULL;
    m_hInstance=NULL;
    return killSuccess;
}


void OpenGLWindow::DrawGrid(int nrOfLines, float gridSize)
{
	float min = -gridSize * nrOfLines/2;
	float max = gridSize* nrOfLines/2;
	glBegin(GL_LINES);
	glColor3ub(255,0,0);
	for ( int x = 0; x < nrOfLines/2; ++x)
	{
		glVertex3f(x*gridSize,min,0);
		glVertex3f(x*gridSize,max,0);
	}
	glColor3ub(255,115,115);
	for ( int x = 1; x < nrOfLines/2; ++x)
	{
		glVertex3f(-x*gridSize,min,0);
		glVertex3f(-x*gridSize,max,0);
	}
	glColor3ub(0,255,0);
	for ( int y = 0; y < nrOfLines/2; ++y)
	{
		glVertex3f(min,y*gridSize,0);
		glVertex3f(max,y*gridSize,0);
	}
	glColor3ub(200,255,200);
	for ( int y = 1; y < nrOfLines/2; ++y)
	{
		glVertex3f(min,-y*gridSize,0);
		glVertex3f(max,-y*gridSize,0);
	}

	glEnd();
}


void OpenGLWindow::Draw()
{
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);	// Clear Screen And Depth Buffer
	glLoadIdentity();									// Reset The Current Modelview Matrix
	DrawGrid(20,0.2f);

	for( auto it = m_RenderComponents.begin(); it != m_RenderComponents.end(); ++it)
	{
		(*it)->Draw();
	}
}